index.vue 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. <template>
  2. <div
  3. ref="page"
  4. class="flex flex-col gap-2 p-2 overflow-y-auto bg-background-200"
  5. >
  6. <div
  7. ref="header"
  8. class="right-0 left-0 top-0 fixed"
  9. >
  10. <img
  11. v-if="news"
  12. :src="news.preview.length > 0 ? news.preview : './notfound.png'"
  13. class="w-full object-cover object-center absolute -z-10"
  14. :style="`height: ${imageHeight}px`"
  15. >
  16. <div
  17. v-else
  18. class="w-full object-cover object-center absolute -z-10 loading"
  19. :style="`height: ${imageHeight}px`"
  20. />
  21. <div
  22. class="flex w-full flex-col"
  23. :style="`
  24. height: ${imageHeight}px;
  25. background: linear-gradient(to bottom, #212121, rgba(33,33,33,${blur/10.5}), #212121)
  26. `"
  27. >
  28. <div
  29. ref="blurry"
  30. :class="`w-full h-full transition-all`"
  31. :style="`backdrop-filter: blur(${blur}px); background-color: rgba(33,33,33,0);`"
  32. >
  33. <IArrowLeftAlt
  34. class="absolute top-0 left-2 w-12 h-12 hover:opacity-50 duration-150 z-20 animate__animated animate__fadeInLeft animate__duration"
  35. :style="direction === 'right' && lengthX < -100 ? `left: 20px;` : ''"
  36. @click="$router.back()"
  37. />
  38. <div
  39. v-if="news"
  40. class="font-semibold absolute bottom-2.5 truncate w-full pr-4 show"
  41. :style="`padding-left: ${padLeft}px; font-size: ${textSize}px; line-height: ${lineHeight}px; ${titleStyles};`"
  42. v-text="news.title"
  43. />
  44. </div>
  45. </div>
  46. </div>
  47. <div
  48. v-if="news"
  49. class="flex flex-col min-h-screen h-[2000px] news-content"
  50. :style="`padding-top: ${headerHeightMax}px`"
  51. v-html="news.content"
  52. />
  53. <div
  54. v-else
  55. class="flex flex-col min-h-screen items-center justify-center news-content"
  56. :style="`padding-top: ${headerHeightMax}px`"
  57. >
  58. <ILoader class="w-10 h-10" />
  59. </div>
  60. </div>
  61. </template>
  62. <script setup lang="ts">
  63. import { useWindowScroll, useSwipe } from '@vueuse/core'
  64. import type { News } from '~/types/news'
  65. definePageMeta({
  66. middleware: ['user-only'],
  67. layout: 'news',
  68. })
  69. const headerHeightMax = 275
  70. const headerHeightMin = 50
  71. const backButtonHeight = 48
  72. const titleLeftPaddingMin = 10
  73. const imageHeight = ref(headerHeightMax)
  74. const blur = ref(0)
  75. const page = ref(null)
  76. const padLeft = ref(titleLeftPaddingMin)
  77. const textSize = ref(24)
  78. const lineHeight = ref(32)
  79. const titleStyles = ref('')
  80. const route = useRoute()
  81. const { $api } = useNuxtApp()
  82. const news = ref<News>()
  83. const { y } = useWindowScroll({ behavior: 'smooth' })
  84. const { direction, lengthX } = useSwipe(page, {
  85. onSwipeEnd() {
  86. if (direction.value === 'right' && lengthX.value < -100) useRouter().back()
  87. },
  88. })
  89. watch(y, (val) => {
  90. imageHeight.value = Math.max(headerHeightMin, headerHeightMax - val)
  91. blur.value = Math.ceil((headerHeightMax - imageHeight.value) / 25)
  92. if (imageHeight.value > backButtonHeight * 2) {
  93. padLeft.value = titleLeftPaddingMin
  94. textSize.value = 24
  95. lineHeight.value = 32
  96. }
  97. else {
  98. padLeft.value = titleLeftPaddingMin + (-imageHeight.value + (backButtonHeight * 2)) * 1.5
  99. textSize.value = Math.min(24, imageHeight.value - backButtonHeight + 16)
  100. lineHeight.value = 32
  101. }
  102. })
  103. onMounted(async () => {
  104. news.value = await $api.news.getPost(route.params.id)
  105. })
  106. </script>